﻿#region copyright
//<copyright>
// Copyright(C) 2012 TrackerRealm Corporation
// This file is part of the open source project - Jazz. http://jazz.codeplex.com
// 
// Jazz is open software: you can redistribute it and/or modify it 
// under the terms of the GNU Affero General Public License (AGPL) as published by 
// the Free Software Foundation, version 3 of the License.
// 
// Jazz is distributed in the hope that it will be useful, 
// but WITHOUT ANY WARRANTY; without even the implied warranty 
// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  
//  See the GNU Affero General Public License (AGPL) for more details.
// 
// You should have received a copy of the GNU General Public 
// License along with Jazz.  If not, see <http://www.gnu.org/licenses/>.
//
// REMOVAL OF THIS NOTICE IS VIOLATION OF THE COPYRIGHT. 
//</copyright>
#endregion
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel;
using System.Reflection;
using System.Runtime.Serialization;
using Newtonsoft.Json.Serialization;
using Newtonsoft.Json;
using TrackerRealm.Jazz.Common;
using TrackerRealm.Jazz.Client;
using TrackerRealm.Jazz.Client.Services;

namespace TrackerRealm.Jazz.Json
{
    #region JazzContainer
    /// <summary>
    /// 
    /// </summary>
    public class JazzContainer
    {
        /// <summary>
        /// 
        /// </summary>
        public jObject JazzObject { get; set; }
    }
    #endregion
    #region jObjectConverter
    /// <summary>
    /// Serializes and Deserializes the actual Jazz object.
    /// </summary>
    public class jObjectConverter : JsonConverter
    {
        private SerializerService serialService;
        bool isFullConverter = true;
        /// <summary>
        /// 
        /// </summary>
        /// <param name="service"></param>
        public jObjectConverter(SerializerService service)
        {
            if (service == null)
                throw new ArgumentNullException("jObjectConverter - the service argument can not be null.", "service");
            this.serialService = service;
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="service"></param>
        /// <param name="isFullConverter"></param>
        public jObjectConverter(SerializerService service, bool isFullConverter)
            :this(service)
        {
            this.isFullConverter = isFullConverter;
        }
        
        /// <summary>
        /// 
        /// </summary>
        /// <param name="writer"></param>
        /// <param name="value"></param>
        /// <param name="serializer"></param>
        public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
        {
            JazzContainer container = (JazzContainer)value;
            jObject jazzObj = container.JazzObject;
            writer.WriteStartObject();
            Type t = jazzObj.GetType();
            // ClassName
            writer.WritePropertyName("ClassName");
            writer.WriteValue(t.FullName);
            // AssemblyName - if required
            string s = t.Assembly.FullName;
            string[] sArray = s.Split(',');
            if (sArray[0] != JazzServices.GetNamespaceFromFullClassName(t.FullName))
            {
                writer.WritePropertyName("AssemblyName");
                writer.WriteValue(sArray[0]);  // just the name
            }
            // ID 
            writer.WritePropertyName("id");
            writer.WriteValue(jazzObj.ID);
            // the rest of the fields
            // 
            // Duplicate field names are possible - use short name unless duplicate is found
            Dictionary<string, FieldInfo> fieldDict = new Dictionary<string, FieldInfo>();
            do
            {
                FieldInfo[] members = t.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
                foreach (FieldInfo m in members)
                {
                    if (fieldDict.ContainsKey(m.Name))
                    {
                        fieldDict.Add(m.DeclaringType.Name + "." + m.Name, m);
                    }
                    else
                    {
                        fieldDict.Add(m.Name, m);
                    }
                }
                t = t.BaseType;
            }
            while (t != typeof(object)); 
            foreach (KeyValuePair<string, FieldInfo> kvp in fieldDict)
            {
                FieldInfo m = kvp.Value;
                if (!m.IsSerializeIgnoreAttribute() && m.Name != Constants_JazzObjectFields.ID)
                {
                    if (!this.isFullConverter && !m.IsBaseFieldAttribute()) continue;
                    writer.WritePropertyName(kvp.Key);
                    serializer.Serialize(writer, m.GetValue(jazzObj));
                }
            }
            writer.WriteEndObject();
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="reader"></param>
        /// <param name="objectType"></param>
        /// <param name="existingValue"></param>
        /// <param name="serializer"></param>
        /// <returns></returns>
        public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
        {
            // ClassName
            reader.Read();
            string p_ClassName = (string)reader.Value;
            reader.Read();
            string className = (string)reader.Value;
            // 
            reader.Read();
            string assemblyName = "";
            if ((string)reader.Value == "AssemblyName")
            {
                // AssemblyName
                string p_assemblyName = (string)reader.Value;
                reader.Read();
                assemblyName = (string)reader.Value;
                reader.Read();
            }

            Type t;
            if(assemblyName == "")
                t = JazzServices.GetTypeFromName(className);
            else 
                t = JazzServices.GetTypeFromName(assemblyName, className);

            //ID
            string p_id = (string)reader.Value;
            reader.Read();
            Guid id = (Guid)serializer.Deserialize(reader, typeof(Guid));


            jObject jazzObj = this.serialService.Create(id, t);

            // should be cached to increase performance
            Dictionary<string, FieldInfo> fieldDict = new Dictionary<string, FieldInfo>();
            do
            {
                FieldInfo[] members = t.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
                foreach (FieldInfo m in members)
                {
                    if (!(m.IsSerializeIgnoreAttribute()  || m.Name == Constants_JazzObjectFields.ID))
                    {
                        if (!this.isFullConverter && !m.IsBaseFieldAttribute()) continue;
                        if (fieldDict.ContainsKey(m.Name))
                        {
                            fieldDict.Add(m.DeclaringType.Name + "." + m.Name, m);
                        }
                        else
                        {
                            fieldDict.Add(m.Name, m);
                        }
                    }
                }
                t = t.BaseType;
            } while (t != typeof(object));

            for (int i = 0; i < fieldDict.Count; i++)
            {
                reader.Read();
                string pName = (string)reader.Value;
                FieldInfo m = fieldDict[pName];
                reader.Read();
                object o = reader.Value;
                object restoreValue = serializer.Deserialize(reader, m.FieldType);
                m.SetValue(jazzObj, restoreValue);
            }
            // reset the 'Bound' bit only
            JazzFlagsType f = (JazzFlagsType)jObjectFields.Flags.GetValue(jazzObj);
            jObjectFields.Flags.SetValue(jazzObj, f.Reset(JazzFlagsType.Bound));

            this.serialService.SetInitializedFlag(id);
            reader.Read(); // read closing bracket
            return new JazzContainer() {JazzObject = jazzObj };
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="objectType"></param>
        /// <returns></returns>
        public override bool CanConvert(Type objectType)
        {
            if (objectType == typeof(JazzContainer))
                return true;
            return false;
        }
    }
    #endregion
}
